module cpu_top (
    input   logic               clk,
    input   logic               rst_n,
    // ITCM
    output  logic   [7:0]       ITCM_addr,
    input   logic   [31:0]      ITCM_data,
    // DTCM
    output  logic   [7:0]       DTCM_addr,
    output  logic               DTCM_write,
    output  logic   [31:0]      DTCM_wData,
    input   logic   [31:0]      DTCM_rData
);


// signal define
/// if
logic   [31:0]      if_pc;
logic   [31:0]      if_pc_add4;
logic   [31:0]      if_pc_new;
/// id
logic               id_write_reg;
logic               id_write_mem;
logic               id_mem2reg;
logic   [2:0]       id_alu_ctrl;
logic               id_alu_b_imm;
logic               id_rb_waddr_rt;
logic               id_jal;
logic               id_imm_signedEx;
logic   [1:0]       id_fw_dataA;
logic   [1:0]       id_fw_dataB;
logic               id_rs_rt_equ;
logic               id_write_pc_ir;
logic   [1:0]       id_pc_source;

logic   [31:0]      id_pc_add4;
logic   [31:0]      id_pc_j;
logic   [31:0]      id_pc_b;

logic   [31:0]      id_ins;
logic   [5:0]       id_op;
logic   [4:0]       id_rs;
logic   [4:0]       id_rt;
logic   [4:0]       id_rd;
logic   [5:0]       id_func;
logic   [15:0]      id_imm;
logic   [25:0]      id_addr;

logic   [31:0]      id_rb_doutA;
logic   [31:0]      id_rb_doutB;

logic   [31:0]      id_dataA;
logic   [31:0]      id_dataB;
logic   [31:0]      id_imm32;
logic   [4:0]       id_rb_waddr;
/// exe
logic               exe_write_reg;
logic               exe_write_mem;
logic               exe_mem2reg;
logic   [2:0]       exe_alu_ctrl;
logic               exe_alu_b_imm;
logic               exe_jal;

logic   [4:0]       exe_rb_waddr;
logic   [4:0]       exe_rb_waddr2;

logic   [31:0]      exe_pc_add4;
logic   [31:0]      exe_pc_add8;

logic   [31:0]      exe_dataA;
logic   [31:0]      exe_dataB;
logic   [31:0]      exe_imm32;
logic   [31:0]      exe_data;

logic   [31:0]      exe_alu_A;
logic   [31:0]      exe_alu_B;
logic   [31:0]      exe_alu_F;
/// mem
logic               mem_write_reg;
logic               mem_write_mem;
logic               mem_mem2reg;

logic   [4:0]       mem_rb_waddr;

logic   [31:0]      mem_data;
logic   [31:0]      mem_dataB;

logic   [31:0]      mem_dataM;
/// wb
logic               wb_write_reg;
logic               wb_mem2reg;

logic   [4:0]       wb_rb_waddr;

logic   [31:0]      wb_data;
logic   [31:0]      wb_dataM;

logic   [31:0]      wb_rb_wdata;

// IF
always_ff @( posedge clk, negedge rst_n ) begin
    if(!rst_n)
        if_pc <= 32'b0;
    else if(id_write_pc_ir)
        if_pc <= if_pc_new; 
    else
        if_pc <= if_pc;
end

always_comb begin
    if_pc_add4  = if_pc + 32'd4;
    ITCM_addr   = if_pc[7:0];
    case (id_pc_source)
        2'd0: if_pc_new = if_pc_add4;
        2'd1: if_pc_new = id_pc_b;
        2'd2: if_pc_new = id_dataA;
        2'd3: if_pc_new = id_pc_j; 
    endcase
end

// ID
always_ff @( posedge clk, negedge rst_n ) begin
    if(!rst_n)
        id_ins <= 32'd0;
    else if(id_write_pc_ir)
        id_ins <= ITCM_data;
    else
        id_ins <= id_ins;
end

always_ff @( posedge clk, negedge rst_n ) begin
    if(!rst_n) begin
        id_pc_add4  <= 32'd0;
    end
    else begin
        id_pc_add4  <= if_pc_add4;
    end
end

insDecoder u_insDecoder(
    .inst (id_ins  ),
    .op   (id_op   ),
    .rs   (id_rs   ),
    .rt   (id_rt   ),
    .rd   (id_rd   ),
    .func (id_func ),
    .imm  (id_imm  ),
    .addr (id_addr )
);

regBank u_regBank(
    .clk       (~clk         ),
    .rst_n     (rst_n        ),
    .R_Addr_A  (id_rs        ),
    .R_Addr_B  (id_rt        ),
    .Write_Reg (wb_write_reg ),
    .W_Addr    (wb_rb_waddr  ),
    .W_Data    (wb_rb_wdata  ),
    .R_Data_A  (id_rb_doutA  ),
    .R_Data_B  (id_rb_doutB  )
);

ctrl_unit u_ctrl_unit(
    .op            (id_op           ),
    .func          (id_func         ),
    .write_reg     (id_write_reg    ),
    .write_mem     (id_write_mem    ),
    .mem2reg       (id_mem2reg      ),
    .alu_ctrl      (id_alu_ctrl     ),
    .alu_b_imm     (id_alu_b_imm    ),
    .rb_waddr_rt   (id_rb_waddr_rt  ),
    .jal           (id_jal          ),
    .imm_signedEx  (id_imm_signedEx ),

    .rs            (id_rs          ),
    .rt            (id_rt          ),
    .exe_rb_waddr  (exe_rb_waddr2  ),
    .exe_mem2reg   (exe_mem2reg    ),
    .exe_write_reg (exe_write_reg  ),
    .mem_rb_waddr  (mem_rb_waddr   ),
    .mem_mem2reg   (mem_mem2reg    ),
    .mem_write_reg (mem_write_reg  ),
    .fw_dataA      (id_fw_dataA    ),
    .fw_dataB      (id_fw_dataB    ),

    .rs_rt_equ     (id_rs_rt_equ   ),
    .write_pc_ir   (id_write_pc_ir ),
    .pc_source     (id_pc_source   )
);

always_comb begin
    case (id_fw_dataA)
        2'd0: id_dataA = id_rb_doutA;
        2'd1: id_dataA = exe_data;
        2'd2: id_dataA = mem_data;
        2'd3: id_dataA = mem_dataM;
    endcase
    case (id_fw_dataB)
        2'd0: id_dataB = id_rb_doutB;
        2'd1: id_dataB = exe_data;
        2'd2: id_dataB = mem_data;
        2'd3: id_dataB = mem_dataM;
    endcase
    id_imm32        = (id_imm_signedEx) ?{{16{id_imm[15]}}, id_imm} :{16'd0, id_imm};
    id_rb_waddr     = (id_rb_waddr_rt) ?id_rt :id_rd;
    id_pc_j         = {id_pc_add4[31 : 28], id_addr, 2'b00};
    id_pc_b         = id_pc_add4 + {{14{id_imm[15]}}, id_imm, 2'b00};
    id_rs_rt_equ    = ~| (id_dataA ^ id_dataB);
end

// EXE
always_ff @( posedge clk, negedge rst_n ) begin
    if(!rst_n) begin
        exe_write_reg   <= 1'b0;
        exe_write_mem   <= 1'b0;
        exe_mem2reg     <= 1'b0;
        exe_alu_ctrl    <= 3'd0;
        exe_alu_b_imm   <= 1'b0;
        exe_jal         <= 1'b0;

        exe_rb_waddr    <= 5'd0;

        exe_pc_add4     <= 32'd0;

        exe_dataA       <= 32'd0;
        exe_dataB       <= 32'd0;
        exe_imm32       <= 32'd0;
    end
    else begin
        exe_write_reg   <= id_write_reg;
        exe_write_mem   <= id_write_mem;
        exe_mem2reg     <= id_mem2reg;
        exe_alu_ctrl    <= id_alu_ctrl;
        exe_alu_b_imm   <= id_alu_b_imm;
        exe_jal         <= id_jal;

        exe_rb_waddr    <= id_rb_waddr;

        exe_pc_add4     <= id_pc_add4;

        exe_dataA   <= id_dataA;
        exe_dataB   <= id_dataB;
        exe_imm32   <= id_imm32;
    end
end

always_comb begin
    exe_pc_add8 = exe_pc_add4 + 32'd4;

    exe_alu_A   = exe_dataA;
    exe_alu_B   = (exe_alu_b_imm) ?exe_imm32 :exe_dataB;
    exe_data    = (exe_jal) ?exe_pc_add8 :exe_alu_F;

    exe_rb_waddr2 = (exe_jal) ?5'd31 :exe_rb_waddr;
end

ALU u_ALU(
    .ctrl (exe_alu_ctrl ),
    .A    (exe_alu_A    ),
    .B    (exe_alu_B    ),
    .F    (exe_alu_F    )
);

// MEM
always_ff @( posedge clk, negedge rst_n ) begin
    if(!rst_n) begin
        mem_write_reg   <= 1'b0;
        mem_write_mem   <= 1'b0;
        mem_mem2reg     <= 1'b0;

        mem_rb_waddr    <= 5'd0;

        mem_data    <= 32'd0;
        mem_dataB   <= 32'd0;
    end
    else begin
        mem_write_reg   <= exe_write_reg;
        mem_write_mem   <= exe_write_mem;
        mem_mem2reg     <= exe_mem2reg;

        mem_rb_waddr    <= exe_rb_waddr2;

        mem_data    <= exe_data;
        mem_dataB   <= exe_dataB;
    end
end

always_comb begin
    DTCM_write  = mem_write_mem;
    DTCM_addr   = mem_data[7:0];
    DTCM_wData  = mem_dataB;
    mem_dataM   = DTCM_rData;
end

// WB
always_ff @( posedge clk, negedge rst_n ) begin
    if(!rst_n) begin
        wb_write_reg    <= 1'b0;
        wb_mem2reg      <= 1'b0;

        wb_rb_waddr     <= 5'd0;

        wb_data     <= 32'd0;
        wb_dataM    <= 32'd0;
    end
    else begin
        wb_write_reg    <= mem_write_reg;
        wb_mem2reg      <= mem_mem2reg;

        wb_rb_waddr     <= mem_rb_waddr;

        wb_data     <= mem_data;
        wb_dataM    <= mem_dataM;
    end
end

always_comb begin
    wb_rb_wdata = (wb_mem2reg) ?wb_dataM :wb_data;    
end

// make verilator happy
logic [23:0] unused;
always_comb begin
    unused = {mem_dataB[31:8]};
end

endmodule
